package br.edu.ufcg.fpc;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import br.edu.ufcg.fpc.sequential.SequentialCode;
import br.edu.ufcg.fpc.util.Util;

/**
 * Classe principal do sistema.
 */
public class Main {
	
	private static final double NANOSSECOND = 1000000000d;
	
	private static final String SYNCHRONIZED = "synchronized";
	private static final String BAKERY_LOCK = "bakery_lock";
	private static final String REENTRANT_LOCK = "reentrant_lock";
	
	private static final String LOCK_OPTION = "-l";
	
	private static final String FAIR_OPTION1 = "-f";
	private static final String FAIR_OPTION2 = "--fair";
	
	private static final String ATOMIC_OPTION1 = "-a";
	private static final String ATOMIC_OPTION2 = "--atomic-op";
	
	private static final String NUMBER_OF_THREADS = "-t";
	
	private static final String NUMBER_OF_EXECUTIONS = "-n";
	
	private static final String SEQUENTIAL = "-s";
	private static final String SEQUENTIAL_OP2 = "--sequential";
	
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, InterruptedException {
		if (args.length == 0 || args[0].equals("-h") || args[0].equals("--help")) {
			printUsage();
		}
		
		String lastOption = null;
		Class<? extends CriticalRegion> lockType = null;
		boolean option = false;
		boolean sequential = false;
		Integer nThreads = null;
		
		Integer nExecutions = null;
		
		//Realiza parse dos parametros
		for (String argument : args) {
			boolean error = true;

			switch (argument) {
			case LOCK_OPTION:
				if (lastOption != null || lockType != null)
					printUsage();
				lastOption = LOCK_OPTION;
				error = false;
				break;
				
			case SYNCHRONIZED:
				if (!lastOption.equals(LOCK_OPTION) || lockType != null)
					printUsage();
				lockType = SynchronizedCR.class;
				error = false;
				break;
				
			case BAKERY_LOCK:
				if (!lastOption.equals(LOCK_OPTION) || lockType != null)
					printUsage();
				lockType = BakeryLockCR.class;
				error = false;
				break;
				
			case REENTRANT_LOCK:
				if (!lastOption.equals(LOCK_OPTION) || lockType != null)
					printUsage();
				lockType = ReentrantLockCR.class;
				error = false;
				break;

			case FAIR_OPTION1:
				if (lockType == null || !lockType.equals(ReentrantLockCR.class))
					printUsage();
				option = true;
				error = false;
				break;
				
			case FAIR_OPTION2:
				if (lockType == null || !lockType.equals(ReentrantLockCR.class))
					printUsage();
				option = true;
				error = false;
				break;
				
			case ATOMIC_OPTION1:
				if (lockType == null || !lockType.equals(BakeryLockCR.class))
					printUsage();
				option = true;
				error = false;
				break;
				
			case ATOMIC_OPTION2:
				if (lockType == null || !lockType.equals(BakeryLockCR.class))
					printUsage();
				option = true;
				error = false;
				break;
				
			case NUMBER_OF_THREADS:
				if ((lockType == null && sequential == false) || 
						lastOption.equals(NUMBER_OF_THREADS) || nThreads != null)
					printUsage();
				lastOption = NUMBER_OF_THREADS;
				error = false;
				break;
				
			case NUMBER_OF_EXECUTIONS:
				if ((lockType == null && sequential == false) || 
						lastOption.equals(NUMBER_OF_EXECUTIONS) || nExecutions != null)
					printUsage();
				lastOption = NUMBER_OF_EXECUTIONS;
				error = false;
				break;
				
			case SEQUENTIAL:
				if (lockType != null || lastOption != null)
					printUsage();
				lastOption = SEQUENTIAL;
				sequential = true;
				error = false;
				break;
				
			case SEQUENTIAL_OP2:
				if (lockType != null || lastOption != null)
					printUsage();
				lastOption = SEQUENTIAL;
				sequential = true;
				error = false;
				break;
				
			default:
				Integer i = Util.toInteger(argument);
				if (lastOption != null && lastOption.equals(NUMBER_OF_THREADS)
						&& i != null) {
					nThreads = i;
					lastOption = argument;
					error = false;
				} else if (lastOption != null && lastOption.equals(NUMBER_OF_EXECUTIONS)
						&& i != null) {
					nExecutions = i;
					lastOption = argument;
					error = false;
				}
				
				if (error)
					printUsage();
				break;
			}
		}
		
		nThreads = nThreads != null? nThreads : 2;
		nExecutions = nExecutions != null? nExecutions : 1;
		
		List<Double> times = new ArrayList<>();
		double elapsedTime = 0;
		
		for (int i=0; i < nExecutions; i++) {
			if (sequential) {
				elapsedTime = executeSequential();
				times.add(elapsedTime);
			} else {
				elapsedTime = executeParallel(lockType, option, nThreads);
				times.add(elapsedTime);
			}
			
			System.out.printf("Total time: %1.2f s\n", (elapsedTime/NANOSSECOND));
		}
		
		if (nExecutions > 1) {
			System.out.println("\n=================================\n");
			double mean = Util.calculateMean(times);
			System.out.printf("Mean = %1.2f s\n", mean/NANOSSECOND);
			double standardDeviation = Util.calculateStandardDeviation(times, mean);
			System.out.printf("Standard deviation = %1.2f s\n", standardDeviation/NANOSSECOND);
			System.exit(0);
		}
	}

	private static double executeParallel(
			Class<? extends CriticalRegion> lockType, boolean option,
			Integer nThreads) throws InstantiationException,IllegalAccessException, InterruptedException {
		
		MutualExclusion m = new MutualExclusion();
		Util.writeToFile(new File("increment.txt"), null);
		
		long before = System.nanoTime();
		m.execute(nThreads, lockType, option);
		double elapsedTime = System.nanoTime() - before;
		return elapsedTime;
	}

	private static double executeSequential() {
		SequentialCode s = new SequentialCode();
		long before = System.nanoTime();
		s.increment();
		double elapsedTime = System.nanoTime() - before;
		return elapsedTime;
	}

	private static void printUsage() {
		System.out.println("Usage: java -jar MutualExclusion.jar -l lock [options]\n");
		System.out.println("Options and arguments:");
		System.out.println("-l <lock>    :  Specifies the protection type of the critical region.");
		System.out.println("                The options are synchronized, bakery_lock or reentrant_lock.");
		System.out.println("-h --help    :  Prints this help and exits.");
		System.out.println("options:");
		System.out.println(" -a          :  Indicates that the lock must use atomic operations.");
		System.out.println(" --atomic-op    Only used with the option -l bakery_lock.");
		System.out.println(" -f --fair   :  Indicates that the lock must be fair.");
		System.out.println("                Only used with the option -l reentrant_lock.");
		System.out.println(" -n <number> :  Specifies the number of executions (default=1).");
		System.out.println(" -s          :  Runs sequential code.");
		System.out.println(" -t <number> :  Specifies the number of threads (default=2) that will compete");
		System.out.println("                for the critical section .");
		System.exit(0);
	}
	
}
